package com.EthanYankang;

import lombok.Data;

import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import java.io.IOException;
import java.io.InputStream;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Semaphore;

/**
 * @Author: Ethan Yankang
 * @Program: diy-tomcat
 * @Date: 2024-09-07 20:31
 **/
/*
 * @description 传输层链接,利用Socket来连接TCP
 * @params
 * @return
 */
@Data
public class TCP {
    static StringBuilder method = new StringBuilder();//记录请求方法名
    static StringBuilder url = new StringBuilder();//记录请求的URL
    static StringBuilder protocolVersion = new StringBuilder();//记录请求的协议版本
    static StringBuilder requestHead_fieldName = new StringBuilder();//每一个请求头的字段名
    static StringBuilder requestHead_fieldValue = new StringBuilder();//每一个请求头的值
    static Map<String,String> map = new HashMap<>();//记录请求的请求头
    static StringBuilder requestData = new StringBuilder();//记录请求的数据
    private static Request request;// 将http请求连接的协议解析出来封装成请求对象
    private static Response response;
    static  int i=0;

    public void start() throws IOException, InterruptedException {
        //   线程池来处理连接
        ExecutorService es = Executors.newFixedThreadPool(20);
        //   Socket连接  BIO方式
        Socket socket = new ServerSocket(8080).accept();
        //   这里线程不断处理Socket连接，提交。使用静态内部类完成。
        while (true){
                es.submit(new ProcessSocket(socket));
                //使用睡眠机制来使得对象线程的创建阻塞，创建的少了，就不会溢出了！！(和线程的高并发没关系。)
                Thread.sleep(1000);
        }
    }

    //静态内部类处理多线程
    static class ProcessSocket implements Runnable{
        private Socket socket;

        public ProcessSocket(Socket socket) {
            this.socket= socket;
        }
        //使用Semphore信号量来限制线程的执行数量
        Semaphore semaphore = new Semaphore(1);
        @Override
        public void run() {
            try {
                //注意这里并不能解决内存溢出
                semaphore.acquire();
                System.out.println("建立了连接,第"+i+++"次");
                processSocket(socket);

            } catch (IOException e) {
                throw new RuntimeException(e);
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            } finally {
             semaphore.release();
            }
        }

        private void processSocket(Socket socket) throws IOException {//DO-Something
        //    读数据。写数据。
        //    读数据————这里读数据首先会建立连接，所以数据刚开始一定是连接协议的内容。
        //    浏览器里面的http协议是数据的在浏览器访问时的格式，一种规范。
        //    分为请求行，请求提体，请求方法等等。最终这些话数据就变成字节通过Socket连接，发送给Tomcat就仅仅读取Socket的连接处的信息就行了。
            try {
                InputStream inputStream = socket.getInputStream();
                byte[] bytes=new byte[1024];
                //读取字节数据(因为数据是以字节传输的，只能用这种格式接受。但是后面可以char强转。)
                inputStream.read(bytes);
                //将字节数据转化成char字符，就是我们的协议！
                //printProtocol(bytes);
                //Byte2_01(bytes);//字节流开关
                analyzeProtocol(bytes);//解析请求的各种部分
                doServlet(method,url,protocolVersion);//将解析出来的部分封装成请求体，传入servlet并执行。
            }catch (IOException e){
                throw new IOException();
            } catch (ServletException e) {
                throw new RuntimeException(e);
            }
        }


        /*
         * @description 将解析出来的请求方法、URL、HTTP协议封装成request对象，并创建对象，传入servlet中进行处理
         * @params
         * @return
         */
        public void doServlet(StringBuilder method,StringBuilder URL,StringBuilder protocolVersion) throws ServletException, IOException {
            request = new Request(method.toString(), URL.toString(), protocolVersion.toString(),socket);
            response=new Response(request);
            HttpServlet diyHttpServlet = new DIYHttpServlet();
            //System.out.println("在进入Service方法之前，request的信息\t方法："+request.getMethod());
            //调用servlet的DoGet方法(自动封装在了Service()里面        z这里就是没有执行！！！！
            diyHttpServlet.service(request,response);//注意类型匹配，所以要遵循标准的HttpRequest对象的规则，那就直接实现它！

            //真正发送响应
            response.complete();
        }

        private void analyzeProtocol(byte[] bytes) {
            int begin=0;
            //int end=0;
            int count=0;
            for (int pos = begin; pos <bytes.length; pos++) {
                switch (count){
                    case 0://获取请求方法

                         if ((char)bytes[pos]!=' ') {
                             method.append((char) bytes[pos]);
                         }else {
                             System.out.println("请求方法:" + method);
                             count++;
                         }

                    break;
                    case 1://获取URL
                        url.append((char)bytes[pos]);
                        if ((char)bytes[pos]==' '){
                            System.out.println("请求路径:"+url);
                            count++;

                        } break;
                    case 2://获取协议版本
                        protocolVersion.append((char)bytes[pos]);
                        if ((char)bytes[pos]=='\r'){
                            //System.out.println("协议版本:"+protocolVersion);
                            count++;

                        } break;
                    case 3://获取头部字段名
                        if ((char)bytes[pos]=='\n')continue;
                        requestHead_fieldName.append((char)bytes[pos]);
                        if ((char)bytes[pos]==':'){
                            //System.out.printf("头部字段名:" + requestHead_fieldName+"\t\t\t");
                            //requestHead_fieldName.delete(0,requestHead_fieldName.length());
                          count++;
                        } break;

                    case 4://获取头部字段名的值
                        if ((char)bytes[pos]=='\n'&&(char)bytes[pos+1]=='\r'){//这一步直接跳到获取请求数据
                            pos+=2;//消除后面的换行符
                            count=5;
                            continue;
                        }
                        requestHead_fieldValue.append((char)bytes[pos]);
                        if ((char)bytes[pos]=='\r'){
                            map.put(requestHead_fieldName.toString(),requestHead_fieldValue.toString());
                            //System.out.println("字段名的值:"+requestHead_fieldValue);
                            //将之前的请求全部清除，开始下一轮请求
                            requestHead_fieldName.delete(0,requestHead_fieldName.length());
                            requestHead_fieldValue.delete(0,requestHead_fieldValue.length());
                            count--;//会跳回上一步,再次获取请求头部。
                        } break;
                    case 5:
                        requestData.append((char)bytes[pos]);
                        //break;
                        default: break;
               }

            }

            //根据请求找到servlet，实现他的doGet、doPost方法
            //System.out.println("请求数据:" + requestData);
        }


        private void printProtocol(byte[] bytes) {
            for (int i = 0; i < bytes.length; i++) {
                System.out.print((char) bytes[i]);
            }
            System.out.println();
            System.out.println("=====================================");
        }


        private void Byte2_01(byte[] bytes) {
            System.out.println("对应的二进制字节流:");
             for (int i = 0; i < bytes.length; i++) {
                System.out.print(String.format("%8s", Integer.toBinaryString(bytes[i] & 0xFF)).replace(' ', '0') );
                if ((i+1)%25==0)
                System.out.println();
            }
        }
    }
}
